Sub Dev 分享 | Substrate 的 Runtime 升级和去耦合
一块链习是首家区块链技术学习社区,提供最系统的区块链技术课程学习,定期出品有深度的技术观察 + 评论。
《Substrate区块链开发入门》是由Parity 和一块+ 联合出品的全球首个Parity 官方合作课程系列的开发者入门课程。
每周日晚8点,作为课程内容知识拓展——大咖技术分享会,由波卡生态的优质项目方代表自发轮流在线上进行分享,为学员们详细解读一个 Substrate 技术相关内容。
上周日晚,由 Bifrost 核心开发者—— Jamie Deng 在直播间为大家带来第一讲「Substrate的Runtime升级和去耦合」。
内容复盘如下
| 为什么是webassembly
首先,Substrate是使用Rust和webassembly构建的,而编写的Runtime会编译成wasm文件,而这个生成的wams能够执行在VM上,从而做到无叉升级。
高性能。编译出来的wasm在执行时,执行指令接近底层硬件,所有运行速度很快。
沙盒。Webassembly执行在VM上,所有可以做到隔离和热处理等等。
良好的社区支持。工具链和生态得到了社区的大力支持,比如目前rust对webassemly支持地最好。
还有一些其他的优点,比如跨平台,更好的安全性等等。
安全性。当发现链上代码有安全问题,为了能够及时把问题修补,在线上做Runtime的升级时非常必须的。
添加新的接口或者特性。
性能。当我们对Runtime模块进行了性能优化,升级Runtime也是必要的。
截取最新RC5相关Runtime的代码。
/// Runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("node"),
impl_name: create_runtime_str!("substrate-node"),
authoring_version: 10,
// Per convention: if the runtime behavior changes, increment spec_version
// and set impl_version to 0. If only runtime
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 256,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
};
spec_version。比如当有新的接口增加或者删减,从而导致Runtime有共识逻辑上的变化,就需要+1。当做升级时,新版本的Runtime的 spec_version 高于当前运行版本的spec_version,这时链上就会升级到最新的Runtime。
impl_version。这个更多的是非共识逻辑变化的修改,比如对某个接口进行性能优化,优化并不好导致这个接口的输出会发生变化。
其余字段的解释可以阅读这篇文章Runtime Execution。
cargo build --release
cp target/release/substrate . # 拷贝当前编译native binary到当前目录,避免下次编译被覆盖
# 生成wasm路径:target/release/wbuild/node-runtime/node_runtime.compact.wasm
cargo build --release
./substrate --dev
set_code。这个在做本地或者测试网测试使用,快速且方便。不推荐线上服务通过这个接口升级Runtime。
scheduler + set_code。这个升级方式更加合理和弹性,可以在某个块高度,或者周期,优先级上进行升级设置,而且不会造成链上资源的过度消耗。
用scheduler来升级。
修改impl_version,然后进行升级。
.02 Runtime模块的去耦合
冗余。随着工程规模的增加,内部模块和代码的关系会变得复杂。
代码可读性。
调试的便捷性。
当前模块想读取关联模块的storage,可能的定义和调用如下
//定义trait
pub trait Trait: frame_sysmte::Trait + module1::Trait + module2::Trait + ... {
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
...
}
// 读取和写入
{
...
// 读取
let module1_value = <module1::ModuleStorage1<T>>::get(key1);
let module2_value = <module2::ModuleStorage2<T>>::get(key2);
// 写入
<module1::ModuleStorage1<T>>::mutate(&key1, |val| {
// ...
});
<module2::ModuleStorage2<T>>::mutate(&key2, |val| {
// ...
});
...
}
pub trait TokenTrait<AccountId> {
fn decrease(who: &AccountId, symbol: Symbol, amount: u128);
fn increase(who: &AccountId, symbol: Symbol, amount: u128);
fn get_token(who: &AccountId, symbol: Symbol) -> Token;
fn has_token(who: &AccountId, symbol: Symbol) -> bool;
}
impl<T: Trait> token_primitives::TokenTrait<T::AccountId> for Module<T> {
fn decrease(who: &T::AccountId, symbol: Symbol, amount: u128) {
<AccountToken<T>>::mutate((who, symbol), |token| {
token.balance = token.balance.saturating_sub(amount);
});
}
fn increase(who: &T::AccountId, symbol: Symbol, amount: u128) {
<AccountToken<T>>::mutate((who, symbol), |token| {
token.balance = token.balance.saturating_add(amount);
});
}
fn get_token(who: &T::AccountId, symbol: Symbol) -> Token {
<AccountToken<T>>::get((who, symbol))
}
fn has_token(who: &T::AccountId, symbol: Symbol) -> bool {
<AccountToken<T>>::contains_key((who, symbol))
}
}
代码可以参考token-exchange模块部分
// 定义部分
pub trait Trait: frame_system::Trait {
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
// 关键部分之一,定义一个类型实现了TokenTrait
type TokenTrait: TokenTrait<Self::AccountId>;
}
// 读写部分
{
...
ensure!(
T::TokenTrait::get_token(&exchanger, exchange_symbol).balance >= amount,
Error::<T>::ExchangeTooMuch
);
T::TokenTrait::decrease(&exchanger, exchange_symbol, amount);
T::TokenTrait::increase(&exchanger, target_symbol, rmb);
...
}
impl token::Trait for Runtime {
type Event = Event;
}
impl token_exchange::Trait for Runtime {
type Event = Event;
// 这是关键步骤,这个Token类型就是下面construct_runtime宏生成的
type TokenTrait = Token;
}
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = node_primitives::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
…
Token: token::{Module, Call, Storage, Event<T>},
TokenExchange: token_exchange::{Module, Call, Storage, Event<T>},
}
);
如果读者发现文章有不对或者改进地方,欢迎提出,以避免误导大家,谢谢!